/*
 * Copyright (C) 2009, 2010, B3log Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.b3log.nide.action.websocket;

import java.io.IOException;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import org.b3log.latke.Keys;
import org.b3log.nide.Nide;
import org.b3log.nide.model.source.java.JavaClass;
import org.b3log.nide.model.source.java.JavaSourceFile;
import org.b3log.nide.model.WebSocketData;
import org.b3log.nide.service.JavaCodeAssistant;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocket.Outbound;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * Editor socket.
 *
 * @author <a href="mailto:DL88250@gmail.com">Liang Ding</a>
 * @version 1.0.0.3, Nov 29, 2010
 */
public final class EditorSocket implements WebSocket, Observer {

    /**
     * Logger.
     */
    private static final Logger LOGGER =
            Logger.getLogger(EditorSocket.class.getName());
    /**
     * Outbound.
     */
    private Outbound outbound;
    /**
     * Http servlet request.
     */
    private HttpServletRequest request;
    /**
     * Java code assistant.
     */
    private JavaCodeAssistant javaCodeAssistant = JavaCodeAssistant.INSTANCE;

    @Override
    public void update(final Observable o, final Object arg) {
        send(arg.toString());
    }

    /**
     * Sends the specified line of text.
     *
     * @param line the specified line of text
     */
    public void send(final String line) {
        try {
            outbound.sendMessage(line);
        } catch (final IOException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
        }
    }

    @Override
    public void onConnect(final Outbound outbound) {
        LOGGER.log(Level.FINE, "{0} onConnect", getClass().getSimpleName());
        this.outbound = outbound;
    }

    /**
     * On message with the specified frame and data json string.
     *
     * @param frame the specified frame
     * @param data the specified data json string, if the json object has key
     * <ul>
     *   <li>{@linkplain WebSocketData#WEB_SOCKET_GREETING}</li>
     *   just logs, send back like this:
     *   {@code { "sc": "EDITOR_CONNECTED" } }
     *   <li>{@linkplain WebSocketData#WEB_SOCKET_JAVA_CODE_ASSISTANT_GET_METHODS}</li>
     *   the data argument like this:
     *   {@code {
     *     "webSocketJavaCodeAssistantGetMethods": TODO,
     *     "javaSourceCode": "public class Nide {....}",
     *     "fileLineNum": 12,
     *     "fileColumnNum": 15 } }
     *   for get methods, send back like this: {@code {
     *     "sc": "JAVA_CODE_ASSISTANT_GET_METHODS_SUCC",
     *     "javaClassMethods": [ .... ]} }
     *   See {@linkplain JavaCodeAssistant#getMethods(java.lang.String, int, int) }
     *   for more details about "javaClassMethods": [ .... ]
     * </ul>
     *
     * @see JavaCodeAssistant#getMethods(java.lang.String, int, int)
     */
    @Override
    public void onMessage(final byte frame, final String data) {
        LOGGER.log(Level.FINE, "{0} onMessage(), [data={1}]",
                   new Object[]{getClass().getSimpleName(), data});
        final JSONObject resultJSONObject = new JSONObject();

        try {
            final JSONObject requestJSONObject = new JSONObject(data);

            if (requestJSONObject.has(WebSocketData.WEB_SOCKET_GREETING)) {
                LOGGER.log(Level.FINER, "Editor alive[sessionId={0}].",
                           request.getSession(false).getId());
                resultJSONObject.put(Keys.STATUS_CODE, "EDITOR_CONNECTED");

                final JSONArray messages = new JSONArray();
                messages.put("Hello editor :-)");
                resultJSONObject.put("msgs", messages); // XXX: msgs

            } else if (requestJSONObject.has(
                    WebSocketData.WEB_SOCKET_JAVA_CODE_ASSISTANT_GET_METHODS)) {
                LOGGER.finer("Java code assistant get methods event");
                final String javaSourceCode =
                        requestJSONObject.getString(
                        JavaSourceFile.JAVA_SOURCE_CODE);
                final int lineNum = requestJSONObject.getInt(
                        JavaSourceFile.FILE_LINE_NUM);
                final int columnNum = requestJSONObject.getInt(
                        JavaSourceFile.FILE_COLUMN_NUM);
                final JSONArray methods =
                        javaCodeAssistant.getMethods(javaSourceCode, lineNum,
                                                     columnNum);

                resultJSONObject.put(Keys.STATUS_CODE,
                                     "JAVA_CODE_ASSISTANT_GET_METHODS_SUCC");
                resultJSONObject.put(JavaClass.JAVA_CLASS_METHODS, methods);
            }

        } catch (final Exception e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
        }

        String ret = null;
        try {
            ret = resultJSONObject.toString(Nide.INDENT);
        } catch (final JSONException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
        }

        LOGGER.log(Level.FINE,
                   "Java code assistant get methods event result[{0}]", ret);
        try {
            outbound.sendMessage(ret);
        } catch (final IOException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
        }
    }

    @Override
    public void onMessage(final byte frame, final byte[] data,
                          final int offset, final int length) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void onDisconnect() {
        LOGGER.log(Level.FINE, "{0} onDisconnect", getClass().getSimpleName());
    }

    /**
     * Sets the http servlet request.
     *
     * @param request the new http servlet request
     */
    public void setRequest(final HttpServletRequest request) {
        this.request = request;
    }
}
